# -*- coding: UTF-8 -*-
'''
Created on Jan 24, 2010

@author: pguedes
'''
from utils import notification, settings
import xbmc, xbmcgui, xbmcplugin #@UnresolvedImport
import urllib, os, sys, logging
import inspect

log = logging.getLogger("pluginsupport")

CACHE_PATH_FORMAT = "special://masterprofile/Thumbnails/Video/%s/%s"

class PluginHandlerModeCallback:
  def __init__(self, handler):
    self.playable = handler.playable
    self.contentType = handler.contentType
    self.callback = handler
    
  def call(self, params={}):
    """Call this callback
    This method maps the url encoded params into the mode handler function's arguments 
    and then invokes the handler function
    @param params the request params"""
    contentType = self.contentType
    if callable(contentType):
      contentType = contentType(params)
      log.debug("Called content type function and got: %s for %s"%(str(contentType),str(params)))
    return _executeOne(self.callback, params), contentType, self.playable
    
class PluginHandler:
  def __init__(self):
    """Initialize the map of action handlers in this object"""
    self.__callbackMap = {}
    self.__modeCallbackMap = {}
    for k in (getattr(self, x) for x in dir(self)):
      if hasattr(k, "bind_to_mode"):
        self.__modeCallbackMap[k.bind_to_mode] = PluginHandlerModeCallback(k)
      elif hasattr(k, "default_handler"):
        self.__defaultModeCallback = PluginHandlerModeCallback(k)
      elif hasattr(k, "bind_to_action"):
        self.__callbackMap[k.bind_to_action] = k
  
  @staticmethod
  def mode(mode, contentType=None, playable=False):
    """Decorator to register mode handler functions
    @param mode: the mode to register the function for
    @param contentType: the content type for this mode (default: None)
    @param playable: if the content is playable (default: False)"""
    def f(g, m=mode, c=contentType, p=playable):
      g.bind_to_mode = m
      g.playable = p
      g.contentType = c
      return g
    return f

  @staticmethod
  def default():
    """Decorator to register the default mode handler function"""
    def f(g):
      g.default_handler = True
      g.playable = False
      g.contentType = None
      return g
    return f

  @staticmethod
  def action(action):
    """Decorator to register action handler functions
    @param action: the action to register the function for"""
    def f(g, a=action):
        g.bind_to_action = a
        return g
    return f
 
  def executeAction(self, params):
    """Execute an action
    @param params the request params"""
    action = params['action']
    del params['action']
    _executeOne(self.__callbackMap[action], params);
    
  def handle(self, params):
    """Handle a request
    @param params the request params
    @return: a list of PluginMovieItem's (or generator)"""
    if not params:
      return self.__handleDefault()
    return self.__handleMode(params)

  def __handleDefault(self):
    """Handle the default plugin call
    @param params the request params"""
    return self.__defaultModeCallback.call()
  
  def __handleMode(self, params):
    """Handle a normal plugin call
    @param params the request params"""
    mode = params['mode']
    del params['mode']
    return self.__modeCallbackMap[mode].call(params)
  
  def isAction(self, params):
    """Test if a request is for an action (out of normal flow)
    @param params the request params
    @return: True if it's an action, false otherwise"""
    pass

  def isCacheable(self, contentType):
    """Test if a content type can be cached (or not if it changes too much)
    @param contentType the content type to test for cache usage
    @return: True if it's cacheable content, false otherwise"""
    pass

class PluginMovieItem:
  __listItem = None
  def __init__(self, name, url, mode=None, metadata=None, tags=None, playable=False, listSize=None):
    self.name = name
    self.url = url
    self.mode = mode
    self.metadata = metadata
    self.tags = tags
    self.playable = playable
    self.listSize = listSize
    
  def getListItem(self, contextMenu=None):
    if self.__listItem: 
      return self.__listItem
    thumb = ""
    labels = { "title": self.name }
    fanart = None
    if self.metadata is not None and self.metadata.hasCode(): 
      thumb = _preChacheThumbnail(self.metadata.getCover(), "Loading cover for : '%s'" % self.name)
      labels = self.metadata.getLabels()
      if self.metadata.hasFanart():
        fanart = _preChacheThumbnail(self.metadata.getFanart(), "Loading fanart for : '%s'" % self.name)
    self.__listItem = xbmcgui.ListItem(self.name + (self.tags or ""), iconImage=thumb, thumbnailImage=thumb, path=self.url)
    self.__listItem.setInfo(type="Video", infoLabels=labels)
    if fanart is not None:
      self.__listItem.setProperty('fanart_image', fanart)

    if not self.isFolder():
      self.__listItem.setProperty('IsPlayable', 'true')
      
    if contextMenu:
      if not self.isFolder():
        contextMenu.append(("Select source", "XBMC.PlayMedia(%s)" % self.getTargetUrl('selectSource')))
      self.__listItem.addContextMenuItems(contextMenu)
    
    return self.__listItem
   
  def isFolder(self):
    return not self.playable
   
  def getTargetUrl(self, action=None):
    """Returns the url for this item to use by XBMC
    The action param is used to generate actiion target url for this item,
    for example for the context menu items.
    @param action: optional action to add to the url"""
    if self.mode:
      argsMap = {"url": self.url, "mode": str(self.mode), "name": self.name}
      if action:
        argsMap['action'] = action
      return encode(argsMap)
    return self.url 
  
  def __str__(self):
    return "PluginMovieItem: {name=%s, url=%s}"%(self.name, self.url);

def _preChacheThumbnail(url, reason="May take a while..."):
  '''Cache an image file (cover or backdrop)
  @param url: the url of the image file to cache locally
  @param reason: the reason to inform the user with, since this is may take a while
  @return: the path to the file cached locally'''
  if url is None or url == "": return ""
  try:
    filename = xbmc.getCacheThumbName(url)
    filepath = CACHE_PATH_FORMAT % (filename[0], filename)
    log.debug("Got thumbnail path '%s' for file '%s'" % (filename, url))
    if not os.path.isfile(filepath):
      notifier = notification.getUserNotifier("Downloading artwork", reason)
      log.debug("Caching thumbnail '%s' for remote file '%s'" % (filename, url))
      urllib.urlretrieve(url, filepath)
      urllib.urlcleanup()
      notifier.close()
    log.debug("Returning thumb '%s' for file '%s'" % (filename, url))
    return filepath
  except:
    log.exception("Failed to cache thumbnail: %s" % url)
    return url

def list(listItems):
  """List some PluginMovieItems in XBMC
  @param listItems: an iterable of PluginMovieItem instances"""
  for item in listItems:
    try:
      print("huh%s"%str(item))
    except Exception, e:
      print("something weird happening here..."+str(e))
    targetUrl = item.getTargetUrl()
    count = item.listSize
    contextItems = [("Reload listing", "Container.Refresh")]
    loadTargetName = 'Re-load info'
    if item.metadata is None or not item.metadata.hasCode():
      loadTargetName = 'Load info'
    if not settings.isSet("metadata-query-skip"):
      contextItems.append((loadTargetName, "XBMC.RunPlugin(%s)" % item.getTargetUrl('loadInfo')))
    listItem = item.getListItem(contextItems)
    xbmcplugin.addDirectoryItem(int(sys.argv[1]), targetUrl, listItem, item.isFolder(), count)

def play(playableItems):
  """Play some PluginMovieItems in XBMC
  If the list contains more than one element, this function will set the resolved url
  to the first part of the file and enqueue the rest of the parts in XBMC's playlist
  @param playableItems: an iterable of PluginMovieItem instances to play"""
  if playableItems and len(playableItems) > 1:
    playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
    # we need to append the parts to the playlist
    log.debug("Playing items: %s" % str(playableItems))
    position = playlist.getposition()
    otherParts = playableItems[1:]
    for index in range(len(otherParts)):
      try:
        playable = otherParts[index]
        partNumber = index + 2
        log.debug("Appending part %s: %s %s" % (str(partNumber), playable.getTargetUrl(), str(position + partNumber)))
        playlist.add(playable.getTargetUrl(), playable.getListItem(), position + partNumber)
        log.debug("Appended part %s: %s" % (str(partNumber), playable.getTargetUrl()))
      except:
        log.exception("Failed adding an item to playlist")
  log.debug("setting resolved url: %s" % playableItems[0].getTargetUrl())
  xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, playableItems[0].getListItem())

def __mapArgs(functionArgs, pluginParams):
  '''Map the plugins invocation arguments to the handler function arguments
  The arguments are matched by name
  @param functionArgs: The list of arguments the function requires
  @param pluginParams: the available params from the invocation of the plugin (url)
  @return: a dict of arguments to invoke the handler function with'''
  args = {}
  for arg in functionArgs:
    if pluginParams.has_key(arg):
      args[arg] = pluginParams[arg]
  return args

def _executeOne(f, params={}):
  """Execute a function with some params from the plugin
  This method maps the url encoded params into the mode handler function's arguments 
  and then invokes the handler function
  @param params the request params"""
  fargs = inspect.getargspec(f)[0]
  if not fargs:
    return f()
  args = __mapArgs(fargs, params)
  log.debug("Dispatching call to function with args: %s"%str(args)) 
  return f(**args)

def parseArguments(url):
  '''
  Parse the URL arguments into a map
  @param url: a url to parse request arguments from
  @return: a dict with the arguments
  '''
  param = {}
  paramstring = url
  if len(paramstring) >= 2:
    params = url
    cleanedparams = params.replace('?', '')
    if (params[len(params) - 1] == '/'):
      params = params[0:len(params) - 2]
    pairsofparams = cleanedparams.split('&')
    for i in range(len(pairsofparams)):
      splitparams = {}
      splitparams = pairsofparams[i].split('=')
      if (len(splitparams)) == 2:
        param[splitparams[0]] = urllib.unquote_plus(splitparams[1])
  return param

def encode(paramMap):
  '''
  Encode a list of params into a url
  @param paramMap: map of params to encode into url
  @return: encoded url (<base>?<arg1=arg1Val>&...)
  '''
  params = ["%s=%s" % (key, urllib.quote_plus(value)) for key, value in paramMap.items()]
  return sys.argv[0] + "?" + "&".join(params)
